﻿namespace net.zxteam.ZXLexer.Runtime.Internal
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.Text;

	internal class LexerSourceTextReader : TextReader
	{
		private readonly Context _topContext;
		private readonly Stack<Context> _scopeStack = new Stack<Context>();
		private readonly char _indentChar;
		private readonly string _text;

		public string Tail { get { return _text.Substring(CurrentContext.Position); } }

		public LexerSourceTextReader(string text) : this(text, '\t') { }
		public LexerSourceTextReader(string text, char indentChar)
		{
			if (text == null) throw new ArgumentNullException();

			this._text = text;
			this._indentChar = indentChar;

			_topContext = new Context();
		}

		public int Position { get { return CurrentContext.Position; } }
		public int StateInitPosition { get { return CurrentContext.InitPosition; } }

		public IDisposable CreateIndentScope() { return new DisposableScope(this.Indent, this.Dedent); }
		public LexerStateScope CreateStateScope() { var ctx = new Context(this); this._scopeStack.Push(ctx); return ctx; }

		public char ReadChar()
		{
			int ch = Read();
			if (ch < 0) throw new RuntimeLexer.RuntimeLexerException(StateInitPosition, Position);
			return (char)ch;
		}

		public override int Peek()
		{
			if (CurrentContext.Position < _text.Length)
				return _text[CurrentContext.Position + CurrentContext.Indent];

			return -1;
		}
		public override int Read()
		{
			if (CurrentContext.Position >= _text.Length)
				return -1;

			switch (CurrentContext.State)
			{
				case Context.LocationState.LINE_START:
					for (int count = 0; count < CurrentContext.Indent && CurrentContext.Position < _text.Length; ++count)
					{
						if (_text[CurrentContext.Position + count] != _indentChar)
							throw new IndentationException();
					}
					if (_text[CurrentContext.Position + CurrentContext.Indent] == _indentChar)
						throw new IndentationException();

					CurrentContext.Position += CurrentContext.Indent;

					CurrentContext.State = Context.LocationState.TAIL;
					return Read();

				//				case Context.LocationState.DENT:
				//					break;
				case Context.LocationState.TAIL:
					if (CurrentContext.Position < _text.Length)
					{
						int ch = _text[CurrentContext.Position++];

						// Checking for new line...
						if (ch == '\r')
						{
							int nextPos = CurrentContext.Position;
							if(nextPos >= _text.Length || _text[nextPos] != '\n')
								CurrentContext.State = Context.LocationState.LINE_START;
						}
						else if (ch == '\n')
						{
							CurrentContext.State = Context.LocationState.LINE_START;
						}


						return ch;
					}
					return -1;
				default:
					throw new NotSupportedException("State '" + CurrentContext.State + "' is not supported");
			}
		}
		public override string ReadLine()
		{
			StringBuilder sb = new StringBuilder();

			while (true)
			{
				int ch = this.Read();
				if (ch < 0) break;

				// Mac NewLine
				if (ch == '\r')
				{
					ch = CurrentContext.Position < _text.Length ? _text[CurrentContext.Position] : -1;
					// Windows NewLine
					if (ch == '\n')
						this.Read();

					break;
				}

				// Linux NewLine
				if (ch == '\n')
					break;

				sb.Append((char)ch);
			}

			return sb.ToString();
		}

		public class IndentationException : Exception { }

		private void Indent() { ++CurrentContext.Indent; }
		private void Dedent() { --CurrentContext.Indent; }
		private void StateScopeExit(LexerStateScope stateScope, bool commited)
		{
			var topState = _scopeStack.Peek();
			if (topState != stateScope) throw new InvalidOperationException();

			_scopeStack.Pop();

			if (commited)
				this.CurrentContext.Merge(topState);
		}

		private Context CurrentContext
		{
			get
			{
				if (_scopeStack.Count > 0)
					return _scopeStack.Peek();

				return _topContext;
			}
		}

		private class Context : LexerStateScope
		{
			//private int _currentLine;
			//private int _currentPosition;
			private int _indent;
			private int _position;
			private LocationState _state;

			public int Indent { get { return _indent; } set { _indent = value; } }
			public int Position { get { return _position; } set { _position = value; } }
			public LocationState State { get { return _state; } set { _state = value; } }

			public Context()
				: base(0, (s, c) => {/* STUB */})
			{
				this._indent = 0;
				this._position = 0;
				this._state = LocationState.LINE_START;
			}

			public Context(LexerSourceTextReader owner)
				: base(owner.CurrentContext._position, owner.StateScopeExit)
			{
				this._indent = owner.CurrentContext._indent;
				this._position = owner.CurrentContext._position;
				this._state = owner.CurrentContext._state;
			}

			public void Merge(Context anotherContext)
			{
				_indent = anotherContext._indent;
				_position = anotherContext._position;
				_state = anotherContext._state;
			}

			public enum LocationState
			{
				LINE_START,
				DENT,
				TAIL
			}
		}
	}
}
